Bug 369

Bug Description:

org.apache.commons.dbcp.datasources.InstanceKeyObjectFactory#removeInstance method is not synchronized, so instanceMap field could be accessed by multiple threads unsafely.
An exception expected by running the test:

Exception in thread "Thread-0": java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
    at java.util.HashMap$KeyIterator.next(HashMap.java:828)
    at org.apache.commons.dbcp.datasources.InstanceKeyObjectFactory.registerNewInstance(InstanceKeyObjectFactory.java:50)
    at org.apache.commons.dbcp.datasources.Dbcp369$1.run(Dbcp369.java:58)
    at java.lang.Thread.run(Thread.java:662)
    

More details about this bug are at DBCP-369 JIRA page.

Interleaving Description:

Numbers in following code snippets present the global execution sequence.
org.apache.commons.dbcp.datasources.InstanceKeyObjectFactory:
    
t1  t2  synchronized static String registerNewInstance(InstanceKeyDataSource ds) {
            int max = 0;
            Iterator i = instanceMap.keySet().iterator();
            while (i.hasNext()) {
1                Object obj = i.next();
            ...
            }
            ...
        }
        

        static void removeInstance(String key){
    3        instanceMap.remove(key);
        }
        

java.util.HashMap:
    
t1  t2  final Entry<K,V> nextEntry() {
2            if (modCount != expectedModCount)
5                throw new ConcurrentModificationException();
            ...
        }
        
        final Entry<K,V> removeEntryForKey(Object key) {
            ...
            while (e != null) {
                Entry<K,V> next = e.next;
                Object k;
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k)))) {
    4                modCount++;
                    size--;
            ...
 

a) Thread t1 calls registerNewInstance method and comes to 1 to call next.
b) Thread t1 comes to 2 in HashMap class. Now modCount and expectedModCount are equal.
c) Before t1 executes 2, context switched, and thread t2 calls remove method at 3.
d) When t2 calls remove method, it comes to removeEntryForKey method in HashMap class, and executes 4.
e) Context switched, t1 now checks modCount and expectedModCount at 2. They are not equal after step d.
f) T1 comes to 5 and throws exception.

How To Reproduce:

This bug is reproduced under dbcp 1.2 and JDK 1.6_33.
Execute the following scripts to run the test to reproduce the bug (assume the location of the dbcp test project is dbcp_test_home).

Linux:
${dbcp_test_home}/scripts/369.sh [--loops arg]
Windows:
%dbcp_test_home%/scripts/369.bat [--loops arg]

The default argument values will be taken if not specified.For example,
${dbcp_test_home}/scripts/369.sh
is the same as
${dbcp_test_home}/scripts/369.sh --loops 10

Option Function Default Value Valid Values
--loops,-l number of loops. 10 integer